﻿using System;
using log4net;
using System.Reflection;
using System.IO;
using DotNetNexusSpam.SpamFilter;
using DotNetNexusSpam;
using System.Collections.Generic;
using System.Net.Mail;
using CDO;

namespace DotNetNexusSpam
{
    /// <summary>
    /// Core class that contains the spam filtering engine.
    /// </summary>
    class SpamFilterEngine
    {
        private ILog _log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
        private string _startupPath = "";
        private string _spamTagForEmailSubject = "";
        private string _emailForNewSpam = "";
        private bool _activaSpecialCharFilter = false;
        private bool _loadWasSuccesFull = true;
        private bool _emailShouldBeChecked = true;
        private CDOMessageWrapper _email;
        private string _emailConverted = "";

        private string _idMessage = "";
        private string _connectorCode = "";
        private string _emailPath = "";

        private const string SETTINGS = "settings.txt";
        
        public SpamFilterEngine(string idMessage, string connectorCode)
        {
            _idMessage = idMessage;
            _connectorCode = connectorCode;
            
            _startupPath = AppDomain.CurrentDomain.BaseDirectory;
            
            LoadSettings();
            LoadMailEnablePath();
            LoadEmailFromFileSystem();            
            if (_loadWasSuccesFull) {
                DefineIfMailShouldBeFiltered();
            } else 
                _log.Warn("Filter engine could not be loaded successfully. Please fix the problems mentioned in the previous messages.");            
        }

        /// <summary>
        /// Pass email through all filters. 
        /// Once a filter stated the email should be deleted, all the other filters will be discarded.
        /// email message. 
        /// </summary>
        public void StartFilter(){
            // loop through all filters and grab their output values
            // to determine what to do with the email
            if (_loadWasSuccesFull && _emailShouldBeChecked)
            {
                _log.Debug("Pass email through all filters.");
                // load filters
                Type ti = typeof(FilterBase);
                FilterBase filter = null;
                bool subjectWasAlreadyTagged = false;
                foreach (Type t in Assembly.GetExecutingAssembly().GetTypes())
                {
                    if (t.BaseType.Name == FilterBase.NAME) {
                        ConstructorInfo ci = t.GetConstructor(new Type[] { typeof(CDOMessageWrapper), typeof(string) });
                        if (ci != null)
                        {
                            filter = (FilterBase)ci.Invoke(new object[] { _email, _startupPath });
                            filter.Fire();
                            if (filter.IsSpam)
                            {
                                if (filter.ActionToTake == FilterAction.DELETE_EMAIL)
                                {
                                    _log.Info(String.Format("Filter '{0}' will delete spam (to: {1}, from: {2}, subject: {3}).", filter.Name, _email.CDOMessage.To, _email.CDOMessage.From, _email.CDOMessage.Subject));
                                    FileUtil.Delete(_emailPath);
                                    break;
                                }
                                else if (!subjectWasAlreadyTagged && filter.ActionToTake == FilterAction.ADD_TAG_TO_SUBJECT)
                                {
                                    // Mail's subject will be tagged as spam (*SPAM*) and made available for client.
                                    // Filter processing continues since another rule could still 
                                    // require the email to get deleted.
                                    //_log.Info(String.Format("Filter '{0}' will add a tag to mail's subject (to: {1}, from: {2}, subject: {3}).", filter.Name, _email.CDOMessage.To, _email.CDOMessage.From, _email.CDOMessage.Subject));
                                    //CDOUtil.TagSubject(_email, _emailPath, _spamTagForEmailSubject);
                                    //subjectWasAlreadyTagged = true;
                                }
                                else if (!subjectWasAlreadyTagged && filter.ActionToTake == FilterAction.ADD_TAG_TO_SUBJECT_SA)
                                {
                                    // Mail's subject will be tagged as spam (*SPAM SA*) and made available for client.
                                    // Filter processing continues since another rule could still 
                                    // require the email to get deleted.
                                    _log.Info(String.Format("Filter '{0}' will add a tag to mail's subject (to: {1}, from: {2}, subject: {3}).", filter.Name, _email.CDOMessage.To, _email.CDOMessage.From, _email.CDOMessage.Subject));
                                    CDOUtil.TagSubject(_email, _emailPath, "*SPAM ASSASSIN*");
                                    subjectWasAlreadyTagged = true;
                                }
                            }
                            else {
                                _log.Debug(String.Format("Email was not flagged as spam by {0}.", filter.Name));    
                            }
                        }
                    }
                }
            }
            _log.Debug("Engine terminates filtering.");    
        }

        /// <summary>
        /// Tries to locate the email inside the file system.
        /// </summary>
        private void LoadEmailFromFileSystem() {
            _emailPath = _mailEnableDir + "\\Queues\\" + _connectorCode + "\\Inbound\\Messages\\" + _idMessage;
            //_emailPath = @"C:\SA\SPAM_RMTAP.MAI";
            if (File.Exists(_emailPath))
            {
                _log.Debug(String.Format("Email file loaded succesfully from {0}", _emailPath));
                _email = CDOUtil.ConvertEmailToCDO(_emailPath);

                if (_email == null)
                {
                    _log.Error("Email file could not be parsed succesfully into a CDO message.");
                    _loadWasSuccesFull = false;
                }
            }
            else
            {
                _log.Info(String.Format("Email file could not be loaded from {0}", _emailPath));
                _loadWasSuccesFull = false;
            }
        }

        public void DefineIfMailShouldBeFiltered()
        {
            // Load inboxes and domains the filtering engine should be monitoring.
            // If no inbox or domain is loaded, all emails will get filtered.
            InboxToWatch oInbox = new InboxToWatch();
            DomainToWatch oDomain = new DomainToWatch();

            string[] inboxToWatchList = oInbox.LoadInboxList(_startupPath);
            string[] domainToWatchList = oDomain.LoadDomainList(_startupPath);
            
            _emailShouldBeChecked = false;

            // If no inbox or domain is loaded, all emails should get filtered.
            if (oDomain.IsEmpty && oInbox.IsEmpty)
            {
                _emailShouldBeChecked = true;
            }
            else
            {
                // Email's original format contains special chars that should be cleaned.
                _emailConverted = EmailUtil.ConvertToValidEmail(_email.CDOMessage.To);

                // Continue only if email has a valid format.
                if (EmailUtil.IsValidEmail(_emailConverted))
                {
                    _log.Debug(String.Format("Email {0} was succesfully validated.", _emailConverted));
                    if (!oInbox.IsEmpty)
                    {
                        foreach (string inbox in inboxToWatchList)
                        {
                            if (_emailConverted == inbox)
                            {
                                _emailShouldBeChecked = true;
                                break;
                            }
                        }
                    }
                    if (!_emailShouldBeChecked)
                    {
                        // Extract domain from email.
                        string domainFromEmailConverted = EmailUtil.DomainFromEmail(_emailConverted);
                        if (!oDomain.IsEmpty)
                        {
                            foreach (string domain in domainToWatchList)
                            {
                                if (domainFromEmailConverted == domain)
                                {
                                    _emailShouldBeChecked = true;
                                    break;
                                }
                            }
                        }
                    }
                }
                else
                {
                    _log.Error("The email has an incorrect format or could not be parsed.");
                    _loadWasSuccesFull = false;
                }
            }
            if (!_emailShouldBeChecked)
                _log.Warn(String.Format("Email '{0}' will not be filtered since it does not belong to the InboxToWatch or DomainToWatch file.", _emailConverted));
        }

        /// <summary>
        /// Configure:
        /// - the tag that is added to the email's subect considered as spam (default is *SPAM*).
        /// - the email that the engine will be monitoring to complete the spamDelete.txt file.
        /// - boolean flag to turn on or off a special character filter. If the email contents more
        ///   thant 4% of special characters such as "~ ? * | _ ! # +," it will be tagged as spam.
        /// </summary>
        private void LoadSettings()
        {
            string settingsPath = _startupPath + "config\\" + SETTINGS;
            if (File.Exists(settingsPath))
            {
                string settingsContent = "";
                using (StreamReader settingsReader = new StreamReader(settingsPath))
                {
                    settingsContent = settingsReader.ReadToEnd();
                }
                string[] setOfSettings = settingsContent.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
                if (setOfSettings.Length >= 2)
                {
                    _spamTagForEmailSubject = setOfSettings[0];
                    _emailForNewSpam = setOfSettings[1];
                    if (!bool.TryParse(setOfSettings[2], out _activaSpecialCharFilter))
                        _activaSpecialCharFilter = false;
                    _log.Debug("Settings file was loaded succesfully.");   
                }
                else {
                    _log.Warn("Settings file could not be loaded from " + settingsPath);   
                }

            } else {
                _log.Warn("Settings file could not be loaded from " + settingsPath);   
            }
        }

        /// <summary>
        /// Reads the Mail Enable directory path from the registry.
        /// </summary>
        private string _mailEnableDir;
        private const string KEY = @"SOFTWARE\Mail Enable\Mail Enable";
        private const string NAME = "Data Directory";
        private void LoadMailEnablePath()
        {
            _mailEnableDir = RegistryUtil.Read(KEY, NAME);
            if (_mailEnableDir == "")
                _loadWasSuccesFull = false;
        }

        /// <summary>
        /// Flag that indicates wether the filter engine was loaded succsfully.
        /// </summary>
        public bool LoadWasSuccesFull{
            get { return _loadWasSuccesFull; }
        }
    }
}
